package com.wangtianxiang.sxp.dbbase.config.processor;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.wangtianxiang.sxp.dbbase.config.session.SxpSqlSessionFactoryProxy;
import com.wangtianxiang.sxp.dbbase.config.utils.JSONObjectUtil;
import com.wangtianxiang.sxp.dbbase.config.db.SxpDatasourceProperties;
import com.wangtianxiang.sxp.dbbase.config.db.SxpDbConfigProperties;
import com.wangtianxiang.sxp.dbbase.config.db.SxpMybaitsProperties;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.*;

import static org.springframework.util.StringUtils.tokenizeToStringArray;

public class SxpDataBeanRegister extends SxpBeanRegister {
    private static final Logger logger = LoggerFactory.getLogger(SxpDataBeanRegister.class);

    // 注册到spring的dataSource bean后缀
    public static final String BEAN_DATA_SOURCE_SUFFIX = "Datasource";
    // 注册到spring的transactionManager bean后缀
    public static final String BEAN_TRANSACTION_MANAGER_SUFFIX = "TransactionManager";
    // 注册到spring的jdbcTemplate bean后缀
    public static final String BEAN_JDBC_TEMPLATE_SUFFIX = "JdbcTemplate";
    // 注册到spring的sqlSessionFactorySuffix bean后缀
    public static final String BEAN_SQL_SESSION_FACTORY_SUFFIX = "SqlSessionFactory";

    // 注册到spring的sqlSessionFactorySuffix bean后缀
    public static final String BEAN_MYBATIS_SQL_SESSION_FACTORY_SUFFIX = "mybatisSqlSessionFactory";

    // 注册到spring 的mapperScannerConfigurer bean后缀
    public static final String BEAN_MAPPER_SCANNER_CONFIGURER_SUFFIX = "MapperScannerConfigurer";

    private SxpDbConfigProperties sxpDbConfigProperties;

    /**
     * 数据源
     */
    private final Map<String, DataSource> datasourceMap = new HashMap<>();

    /**
     * 自定义注册bean的AnnotatedBeanDefinition
     */
    private final Map<String, AnnotatedBeanDefinition> customBeansBeanDefinition;

    public SxpDataBeanRegister(SxpDbConfigProperties sxpDbConfigProperties, Map<String, AnnotatedBeanDefinition> customBeansBeanDefinition) {
        this.sxpDbConfigProperties = sxpDbConfigProperties;
        this.customBeansBeanDefinition = customBeansBeanDefinition;
    }

    /**
     * register data sources
     *
     * @param registry
     * @param beanNameGenerator
     */
    public void registerDatasource(BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) {
        // 配置数据源
        Map<String, SxpDatasourceProperties> datasources = sxpDbConfigProperties.getDatasources();
        datasources.forEach((name, datasourceProperties) -> {
            Properties properties = JSONObjectUtil.convert(datasourceProperties, Properties.class);
            for (String key : SxpDatasourceProperties.PROPERTIES_IGNORE_FOR_HIKATI) {
                properties.remove(key);
            }
            HikariConfig configuration = new HikariConfig(properties);
            AnnotatedBeanDefinition annotatedBeanDefinition = new AnnotatedGenericBeanDefinition(
                    HikariDataSource.class);
            annotatedBeanDefinition.setDestroyMethodName("close");
            annotatedBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(configuration);

            //alias
            String[] aliasArray = tokenizeToStringArray(datasourceProperties.getDataSourceAlias(),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

            // data source
            String beanName = name + BEAN_DATA_SOURCE_SUFFIX;
            logger.info("### dbbase ### register Datasource,bean:{},jdbcUrl:{}", beanName, datasourceProperties.getJdbcUrl());
            registerBean(registry, beanNameGenerator, beanName, aliasArray, annotatedBeanDefinition);
            datasourceMap.put(beanName, null);
            cacheBeanDefinition(beanName, annotatedBeanDefinition);
        });
    }

    /**
     * register mybatis
     *  @param registry
     * @param beanNameGenerator
     * @param dataSourceBeans
     * @param globalConfigMap
     * @param interceptorsMap
     */
    public void registerMybatisConfig(BeanDefinitionRegistry registry,
                                      BeanNameGenerator beanNameGenerator,
                                      Map<String, DataSource> dataSourceBeans,
                                      Map<String, GlobalConfig> globalConfigMap,
                                      Map<String, List<Interceptor>> interceptorsMap) {
        dataSourceBeans.keySet().forEach(key -> {
            if (key.indexOf(BEAN_DATA_SOURCE_SUFFIX) <= 0) {
                // skip other datasource
                return;
            }
            String dataSourcePropKey = key.substring(0, key.lastIndexOf(BEAN_DATA_SOURCE_SUFFIX));
            if (!datasourceMap.containsKey(key)
                    || !sxpDbConfigProperties.getDatasources().containsKey(dataSourcePropKey)) {
                // skip other datasource
                return;
            }
            if (null != datasourceMap.get(key)) {
                //bean have registered
                return;
            }
            // ############## 1、数据源注册 #################
            DataSource dataSource = dataSourceBeans.get(key);
            // 注册 transactionManager(事务首先注册)
            registerTransactionManager(registry, beanNameGenerator, dataSource, dataSourcePropKey);
            // 注册 Template
            registerJdbcTemplate(beanNameGenerator, registry, dataSource, dataSourcePropKey);

            // ############## 2、Sql 会话工厂注册 #################
            SxpDatasourceProperties prop = sxpDbConfigProperties.getDatasources().get(dataSourcePropKey);
            SxpMybaitsProperties mybatis = sxpDbConfigProperties.getMybatis().getOrDefault(dataSourcePropKey, new SxpMybaitsProperties());

            // 注册 sqlsessionfactory
            String beanName = registerSqlSessionFactory(registry, beanNameGenerator, dataSource, dataSourcePropKey,
                    prop, mybatis,
                    globalConfigMap.getOrDefault("*", globalConfigMap.get(key)),
                    interceptorsMap.getOrDefault("*", interceptorsMap.get(key)));

            // ############## 3、扫描路径注册 #################
            // 注册 Mapper 扫描配置
            registerMapperScannerConfigurer(registry, beanNameGenerator, beanName, dataSourcePropKey, prop);
            datasourceMap.put(key, dataSource);
        });
    }

    /**
     * register JdbcTemplate
     *
     * @param beanNameGenerator
     * @param registry
     * @param dataSource
     * @param dataSourcePropKey
     * @return
     */
    private String registerJdbcTemplate(BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry,
                                        DataSource dataSource, String dataSourcePropKey) {
        AnnotatedBeanDefinition annotatedBeanDefinition = new AnnotatedGenericBeanDefinition(JdbcTemplate.class);
        annotatedBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(dataSource);
        String beanName = dataSourcePropKey + BEAN_JDBC_TEMPLATE_SUFFIX;
        logger.info("### dbbase ### register JdbcTemplate ,bean:{}", beanName);
        registerBean(registry, beanNameGenerator, beanName, null, annotatedBeanDefinition);
        return beanName;

    }

    /**
     * register TransactionManager
     *
     * @param registry
     * @param beanNameGenerator
     * @param dataSource
     * @param dataSourcePropKey
     * @return
     */
    private String registerTransactionManager(BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator,
                                              DataSource dataSource, String dataSourcePropKey) {
        AnnotatedBeanDefinition annotatedBeanDefinition = new AnnotatedGenericBeanDefinition(
                DataSourceTransactionManager.class);
        annotatedBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(dataSource);
        String beanName = dataSourcePropKey + BEAN_TRANSACTION_MANAGER_SUFFIX;
        logger.info("### dbbase ### register TransactionManager ,bean:{}", beanName);
        registerBean(registry, beanNameGenerator, beanName, null, annotatedBeanDefinition);
        cacheBeanDefinition(beanName, annotatedBeanDefinition);
        return beanName;

    }

    /**
     * register SqlSessionFactory
     *
     * @param registry
     * @param beanNameGenerator
     * @param dataSource
     * @param dataSourcePropKey
     * @param globalConfig
     * @param interceptorList
     * @return
     */
    private String registerSqlSessionFactory(BeanDefinitionRegistry registry,
                                             BeanNameGenerator beanNameGenerator,
                                             DataSource dataSource, String dataSourcePropKey,
                                             SxpDatasourceProperties prop,
                                             SxpMybaitsProperties mybatis,
                                             GlobalConfig globalConfig,
                                             List<Interceptor> interceptorList) {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage(prop.getTypeAliasesPackage());
        String beanName = dataSourcePropKey + BEAN_SQL_SESSION_FACTORY_SUFFIX;
        try {
            String[] mapperLocationsArray = tokenizeToStringArray(prop.getLocationPatterns(),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            List<Resource> resources = new ArrayList<>();
            Resource[] resourceArr = null;
            for (String mapperLocations : mapperLocationsArray) {
                resourceArr = resolver.getResources(mapperLocations);
                Collections.addAll(resources, resourceArr);
                if (logger.isDebugEnabled()) {
                    logger.debug("Scanned locationPatterns: '" + mapperLocations + "' for locations");
                }
            }
            // mybatis 属性配置
            if (null != mybatis.getConfigurationProperties()
                    && !mybatis.getConfigurationProperties().isEmpty()) {
                Properties properties = new Properties();
                properties.putAll(mybatis.getConfigurationProperties());
                bean.setConfigurationProperties(properties);
            }

            //支持配置文件方式配置全局mybatis参数
            if (null != mybatis.getConfigLocation() && !mybatis.getConfigLocation().trim().isEmpty()) {
                bean.setConfigLocation(resolver.getResource(mybatis.getConfigLocation().trim()));
            }

            bean.setMapperLocations(resources.toArray(new Resource[]{}));

            if(null!= globalConfig){
                bean.setGlobalConfig(globalConfig);
            }

            if(null != interceptorList && !interceptorList.isEmpty()){
                bean.setPlugins(interceptorList.toArray(new Interceptor[interceptorList.size()]));
            }else{
                // 配置默认分页插件
                MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
                interceptor.addInnerInterceptor(new PaginationInnerInterceptor(null != mybatis.getDbType()? mybatis.getDbType(): DbType.MYSQL));
                bean.setPlugins(interceptor);
            }

            SqlSessionFactory sqlSessionFactory = bean.getObject();

            AnnotatedBeanDefinition annotatedBeanDefinition = new AnnotatedGenericBeanDefinition(
                    SxpSqlSessionFactoryProxy.class);
            annotatedBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory);
            logger.info("### dbbase ### register SqlSessionFactory ,bean:{},resources:{}", beanName, Arrays.toString(mapperLocationsArray));
            registerBean(registry, beanNameGenerator, beanName, mapperLocationsArray, annotatedBeanDefinition);
            cacheBeanDefinition(beanName, annotatedBeanDefinition);
        } catch (Exception e) {
            logger.error("error:", e);
        }
        return beanName;
    }

    /**
     * register MapperScannerConfigurer
     *
     * @param registry
     * @param beanNameGenerator
     * @param sqlSessionFactoryBeanName
     * @param dataSourcePropKey
     * @param prop
     * @return
     */
    private String registerMapperScannerConfigurer(BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator,
                                                   String sqlSessionFactoryBeanName, String dataSourcePropKey, SxpDatasourceProperties prop) {
        AnnotatedBeanDefinition annotatedBeanDefinition = new AnnotatedGenericBeanDefinition(
                MapperScannerConfigurer.class);
        annotatedBeanDefinition.setAttribute("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName);
        annotatedBeanDefinition.setAttribute("basePackage", prop.getBasePackage());
        String beanName = dataSourcePropKey + BEAN_MAPPER_SCANNER_CONFIGURER_SUFFIX;
        logger.info("### dbbase ### register MapperScannerConfigurer,bean:{},basePackage:{}", beanName, prop.getBasePackage());
        registerBean(registry, beanNameGenerator, beanName, null, annotatedBeanDefinition);
        cacheBeanDefinition(beanName, annotatedBeanDefinition);
        return beanName;
    }

    private void cacheBeanDefinition(String beanName, AnnotatedBeanDefinition annotatedBeanDefinition) {
        customBeansBeanDefinition.put(beanName, annotatedBeanDefinition);
    }

    public Map<String, DataSource> getDatasourceMap() {
        return datasourceMap;
    }

}
